package com.cloud.animationtestdemo.widget

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.BitmapRegionDecoder
import android.graphics.Rect
import android.net.Uri
import android.service.quicksettings.Tile
import android.util.LruCache
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
 * @author: njb
 * @date:   2025/3/2 23:30
 * @desc:   描述
 */
class BigImageLoader(
    private val context: Context,
    private val maxCacheSize: Int = 30
    ) {
        private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
        private val cache = object : LruCache<Tile, Bitmap>(maxCacheSize) {
            override fun entryRemoved(
                evicted: Boolean,
                key: Tile,
                oldValue: Bitmap,
                newValue: Bitmap?
            ) {
                oldValue.recycle()
            }
        }

        private var regionDecoder: BitmapRegionDecoder? = null
        private val imageSize = Rect()
        private var baseTileSize = 1024
        private var currentScale = 1.0f

        private val _visibleRect = MutableStateFlow(Rect())
        private val configChanges = MutableSharedFlow<Unit>()

        fun loadImage(uri: Uri): Flow<LoadedTile> = flow {
            initializeDecoder(uri)

            merge(
                _visibleRect,
                configChanges.onStart { emit(Unit) }
            ).flatMapLatest {
                calculateVisibleTiles()
            }.collect { tile ->
                val bitmap = loadTile(tile)
                emit(LoadedTile(tile, bitmap))
            }
        }.catch { e ->
            // Handle errors
        }.flowOn(Dispatchers.IO)

        private suspend fun initializeDecoder(uri: Uri) {
            withContext(Dispatchers.IO) {
                context.contentResolver.openInputStream(uri)?.use { stream ->
                    regionDecoder?.recycle()
                    regionDecoder = BitmapRegionDecoder.newInstance(stream, false)
                    imageSize.set(0, 0,
                        regionDecoder!!.width,
                        regionDecoder!!.height
                    )
                } ?: throw IllegalArgumentException("Invalid URI")
            }
        }

        private fun calculateVisibleTiles(): Flow<Tile> = flow {
            val visibleRect = _visibleRect.value
            val scaledTileSize = (baseTileSize / currentScale).toInt()

            val startX = (visibleRect.left / scaledTileSize).coerceAtLeast(0)
            val endX = (visibleRect.right / scaledTileSize).coerceAtLeast(0)
            val startY = (visibleRect.top / scaledTileSize).coerceAtLeast(0)
            val endY = (visibleRect.bottom / scaledTileSize).coerceAtLeast(0)

            for (x in startX..endX) {
                for (y in startY..endY) {
                    val tile = Tile(x, y, currentScale)
                    if (cache.get(tile) == null) {
                        emit(tile)
                    }
                }
            }
        }

        private suspend fun loadTile(tile: Tile): Bitmap {
            return cache.get(tile) ?: withContext(Dispatchers.IO) {
                val scaledSize = (baseTileSize / tile.scale).toInt()
                val rect = Rect(
                    tile.x * scaledSize,
                    tile.y * scaledSize,
                    (tile.x + 1) * scaledSize,
                    (tile.y + 1) * scaledSize
                ).apply { intersect(imageSize) }

                val options = BitmapFactory.Options().apply {
                    inSampleSize = calculateSampleSize(tile.scale)
                    inPreferredConfig = Bitmap.Config.RGB_565
                }

                regionDecoder!!.decodeRegion(rect, options).also {
                    cache.put(tile, it)
                } ?: throw RuntimeException("Decode failed")
            }
        }

        fun updateVisibleRect(rect: Rect) {
            _visibleRect.value = rect
        }

        fun updateScale(scale: Float) {
            currentScale = scale.coerceIn(0.5f, 8f)
            scope.launch {
                cache.evictAll()
                configChanges.emit(Unit)
            }
        }

        fun release() {
            scope.cancel()
            regionDecoder?.recycle()
            cache.evictAll()
        }

        private fun calculateSampleSize(scale: Float): Int {
            return when {
                scale >= 4 -> 8
                scale >= 2 -> 4
                scale >= 1 -> 2
                else -> 1
            }
        }

        data class Tile(val x: Int, val y: Int, val scale: Float)
        data class LoadedTile(val tile: Tile, val bitmap: Bitmap)
}